Le projet en science des données représente l’aboutissement du certificat en science des données de l’Université TÉLUQ. Ce projet constitue un complément essentiel de formation pratique et vise à favoriser l’autonomie, l’esprit d’analyse et la rigueur méthodologique dans la conduite d’un projet complet en science des données.
En partenariat avec le Secrétariat à l’Internet haute vitesse et aux projets spéciaux de connectivité du (SIHVPSC) Ministère du Conseil Exécutif 1, le projet présenté ici s’inscrit dans un contexte concret de développement territorial numérique, avec comme objectif principal l’identification de zones d’intérêt prioritaires pour le déploiement de la fibre optique (Internet haute vitesse)2. Ce mandat a permis de mettre en œuvre l’ensemble des compétences développées dans les cours du certificat, notamment :
SCI-1031 : Visualisation et analyse de données spatiales,
SCI-1018 : Statistique avec R,
SCI-1017 : Traitement des données massives,
SCI-1421 : Apprentissage machine.
En croisant des données géospatiales, financières et techniques, le projet a exploité des méthodes statistiques, des algorithmes d’apprentissage non supervisé (comme DBSCAN)3 et des outils de traitement de grands volumes de données pour proposer des pistes concrètes d’intervention. L’approche adoptée est fondée sur une démarche rigoureuse : de la collecte des données jusqu’à la communication des résultats, en passant par leur nettoyage, leur exploration et leur modélisation.
Ce projet illustre l’apport stratégique des sciences des données dans la planification d’infrastructures critiques et témoigne du caractère avant-gardiste de la formation offerte dans le cadre du certificat.
L’objectif principal de cette étude est d’identifier des zones d’intérêt prioritaires pour le déploiement de la fibre optique, en analysant la distribution spatiale de points géolocalisés (par exemple, des adresses admissibles à l’Internet haute vitesse). Aussi l’analyse permet de d’estimer les sommes estimées pour brancher les adresses présentes dans les regroupements trouvés par l’algorithme.
Pour ce faire, j’ai utilisé une approche basée sur la détection de regroupements spatiaux grâce à l’algorithme DBSCAN (Density-Based Spatial Clustering of Applications with Noise), reconnu pour sa capacité à détecter des structures de densité sans supposer un nombre de groupes à l’avance.
Les données sources, composées de coordonnées géographiques en degrés décimaux (latitude/longitude), ont d’abord été nettoyées, géocodées et transformées en objets spatiaux. Cela a permis de faciliter les opérations d’analyse géographique et la visualisation cartographique.
L’algorithme DBSCAN repose sur deux paramètres fondamentaux :
eps en degrés décimaux (ESPG:4326) qui représente le rayon maximal autour d’un point pour rechercher des voisins ;
minPts, soit le nombre minimal de points dans ce rayon pour former un regroupement (cluster).
Afin de bien explorer l’espace des paramètres et d’adapter l’algorithme à différents contextes de densité géographique, plusieurs combinaisons de valeurs ont été testées. L’objectif était de faire émerger des regroupements pertinents selon le niveau de concentration des points sur le territoire.
Étant donné que les coordonnées sont exprimées en degrés décimaux (ESPG:4326), une conversion approximative en mètres a été effectuée pour mieux interpréter la portée réelle du rayon eps. Aux latitudes du Québec (~46°N), on estime que :
0,01 degré de latitude ≈ 1,11 km (axe nord-sud) ;
0,01 degré de longitude ≈ 0,78 km (axe est-ouest).
Ainsi, un rayon de recherche (eps) de 0,01 correspond à un voisinage spatial d’environ 780 mètres par 1 110 mètres. En augmentant eps à 0,02 ou 0,03, on élargit le périmètre de recherche jusqu’à environ 2,3 km par 3,3 km.
eps_values <- seq(0.01, 0.06, by = 0.01)
# Approx. des distances en m pour une lat de 46°N
# 1° latitude ≈ 111.32 km
# 1° longitude ≈ 111.32 km × cos(latitude)
lat_m <- 111320 # mètre pour 1° de latitude
lon_m <- 111320 * cos(46 * pi / 180) # mètre pour 1° de longitude à 46°N
# Création du data frame
df_eps_m <- data.frame(
Eps_degre = eps_values,
Distance_NS_m = round(eps_values * lat_m, 0),
Distance_EW_m = round(eps_values * lon_m, 0)
)
colnames(df_eps_m) <- c("Eps (°)", "Distance nord-sud (m)", "Distance est-ouest (m)")
library(knitr)
kable(df_eps_m, format = "html", caption = "Correspondance entre eps (en degrés) et les distances approximatives en mètres à 46°N") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE, position = "center")| Eps (°) | Distance nord-sud (m) | Distance est-ouest (m) |
|---|---|---|
| 0.01 | 1113 | 773 |
| 0.02 | 2226 | 1547 |
| 0.03 | 3340 | 2320 |
| 0.04 | 4453 | 3093 |
| 0.05 | 5566 | 3866 |
| 0.06 | 6679 | 4640 |
Pour évaluer les investissements nécessaires au raccordement des adresses identifiées dans les regroupements générés par l’algorithme, nous nous basons sur les coûts moyens observés dans les programmes gouvernementaux antérieurs de subvention à la connectivité.
Plus précisément, le tableau officiel publié par le gouvernement du Québec dans le cadre de l’initiative Opération haute vitesse présente les montants versés à divers fournisseurs d’accès Internet, ainsi que le nombre de foyers visés. Ce tableau4 permet de calculer un coût moyen global d’environ 5 577 $ par foyer subventionné (826M $ / 148207 adresses).
Dans l’étude, ce montant sert de référence estimative pour évaluer les coûts de branchement des adresses regroupées par l’algorithme DBSCAN. Pour chaque regroupement identifié, on estime ainsi les coûts totaux en multipliant le nombre d’adresses par ce coût moyen unitaire.
Il s’agit d’une estimation basée sur des moyennes passées. Les coûts réels peuvent varier en fonction de la densité du territoire, de la topographie, des infrastructures existantes ou des fournisseurs impliqués. Par exemple, Vidéotron a une moyenne de 6800$ par adresse alors que Coop CSUR a une moyenne de 1800$ par adresse.
L’inflation et l’augmentation récente des coûts de main-d’œuvre, des matériaux et de la logistique ne sont pas prises en compte dans ces estimations.
Cette méthode ne distingue pas les différences entre branchements aériens ou souterrains qui influencent pourtant fortement le coût réel.
Chaque combinaison testée de eps et minPts a permis d’observer différentes configurations de regroupements. L’analyse a porté sur plusieurs dimensions :
le nombre de regroupements détectés ;
la proportion de points classés comme bruit (non regroupés) ;
la répartition géographique des clusters, notamment par région administrative.
Cette approche a permis d’identifier les paramètres les plus efficaces pour faire émerger des zones cohérentes et significatives, à fort potentiel pour des projets d’infrastructure numérique.
La carte suivante présente une vue synthétique des résultats par région administrative, en se basant sur la proportion d’adresses non couvertes par les regroupements générés par l’algorithme DBSCAN.
Chaque point sur la carte correspond au centroïde d’une région administrative du Québec. La taille du cercle est proportionnelle au pourcentage d’adresses non regroupées (c’est-à-dire considérées comme du bruit par l’algorithme), tandis que la couleur va du vert (couverture élevée) au rouge (faible couverture). Ce visuel permet ainsi d’identifier rapidement les régions où les regroupements sont les moins efficaces.
Un encadré s’affiche lorsqu’on clique sur un point, montrant le nom de la région et le pourcentage d’adresses non couvertes.
Cette carte offre une perspective régionale complémentaire à l’analyse détaillée par scénario, et peut aider à cibler les zones nécessitant un ajustement des paramètres ou une attention particulière.
# Lire les centroïdes des régions
df_centroïdes <- read_csv("centroides_regions_quebec.csv")
# Lire le fichier des pourcentages non couverts
df_points <- read_csv("comparatif_points.csv")
# Nettoyer la colonne "pourcentage_non_couvert"
df_points <- df_points %>%
mutate(
pourcentage_non_couvert = as.numeric(gsub("%", "", Pourcentage_non_couvert))
)
# Fusionner sur le code de région
df_merged <- df_centroïdes %>%
left_join(df_points, by = c("Nom" = "Region_ADM"))
# Créer un objet sf à partir des centroïdes
sf_regions <- st_as_sf(df_merged, coords = c("Longitude", "Latitude"), crs = 4326)
# Créer une palette de couleurs rouge → vert (valeur élevée = rouge)
pal <- colorNumeric(
palette = "RdYlGn",
domain = sf_regions$pourcentage_non_couvert,
reverse = TRUE
)
leaflet(sf_regions) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
radius = ~sqrt(pourcentage_non_couvert) * 10,
color = ~pal(pourcentage_non_couvert),
stroke = FALSE,
fillOpacity = 0.7,
popup = ~paste0(
"<strong>", Nom, "</strong><br>",
"Pourcentage non couvert : ", round(pourcentage_non_couvert, 1), "%<br>",
"Nombre d'adresse non désservies: ", Nombre_de_points_cluster,
"Coût estimé de branchement toutes les adresses. : ", formatC(Nombre_de_points_cluster * 5500, format = "f", big.mark = " ", digits = 0), " $"
)
) %>%
addLegend(
"bottomright",
pal = pal,
values = ~pourcentage_non_couvert,
title = "Pourcentage des adresses non couvertes (%)",
opacity = 0.8
)Voici la liste des adresesses réputées non désservries par une desserte internet haute vitesse. Les données proviennent du SIHVPSC.
library(readr)
library(dplyr)
library(leaflet)
library(sf)
library(RColorBrewer)
# Lire les données
setwd("~/Desktop/Teluq/SCI-1402/Data/")
df <- read.csv("resultat_sf.csv", sep = ",", header = TRUE, encoding = "UTF-8")
# Nettoyer les données
df_clean <- df %>%
filter(!is.na(Longitude), !is.na(Latitude), !is.na(munnom)) %>%
mutate(
Longitude = as.numeric(Longitude),
Latitude = as.numeric(Latitude),
Region_ADM = as.factor(Region_ADM)
)
# Générer une palette de couleurs selon munnom
Region_ADM_levels <- levels(df_clean$Region_ADM)
palette_Region_ADM_levels <- colorFactor(palette = brewer.pal(min(length(Region_ADM_levels), 8), "Set1"), domain = Region_ADM_levels)
carte_reg_adm <- leaflet(df_clean) %>%
addTiles(group = "Base") %>%
addWMSTiles(
baseUrl = "https://servicescarto.mern.gouv.qc.ca/pes/services/Territoire/SDA_WMS/MapServer/WMSServer",
layers = "Région administrative",
options = WMSTileOptions(format = "image/png", transparent = TRUE),
attribution = "© MERN - Gouvernement du Québec",
group = "Contours SDA"
) %>%
addCircleMarkers(
lng = ~Longitude,
lat = ~Latitude,
radius = 4,
color = ~palette_Region_ADM_levels(Region_ADM),
fillOpacity = 0.8,
stroke = FALSE,
label = ~paste0("Municipalité: ", munnom, "<br>","Latitude: ", Latitude, "<br>Longitude: ", Longitude)
) %>%
addLegend(
position = "bottomright",
pal = palette_Region_ADM_levels,
values = ~Region_ADM,
title = "Region_ADM",
opacity = 1)Le tableau ci-dessous présente le nombre total de points (adresses) disponibles pour chaque région administrative. Ces points correspondent aux données utilisées pour appliquer l’algorithme DBSCAN dans les analyses précédentes.
Cette information est utile pour évaluer la répartition des données sur le territoire, détecter les régions plus denses ou, au contraire, celles qui sont peu représentées. Cela peut influencer l’interprétation des regroupements obtenus : par exemple, une région avec très peu de points peut naturellement produire moins de regroupements, ou être plus sensible au choix des paramètres.
Les régions sont triées en ordre décroissant selon le nombre de points, ce qui permet d’identifier d’un coup d’œil les zones les plus couvertes dans l’échantillon.
stats_region <- df_clean %>%
filter(!is.na(Region_ADM)) %>%
group_by(Region_ADM) %>%
summarise(
Nombre_points = n()
) %>%
arrange(desc(Nombre_points))
library(kableExtra)
stats_region %>%
kable("html", caption = "Nombre de points par région administrative") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))| Region_ADM | Nombre_points |
|---|---|
| Outaouais | 6789 |
| Montérégie | 4333 |
| Chaudière-Appalaches | 3768 |
| Laurentides | 3445 |
| Estrie | 3200 |
| Lanaudière | 3129 |
| Saguenay–Lac-Saint-Jean | 3072 |
| Montréal | 2923 |
| Centre-du-Québec | 2322 |
| Capitale-Nationale | 2224 |
| Bas-Saint-Laurent | 2156 |
| Abitibi-Témiscamingue | 1921 |
| Mauricie | 1207 |
| Laval | 882 |
| Côte-Nord | 678 |
| Nord-du-Québec | 651 |
| Gaspésie–Îles-de-la-Madeleine | 359 |
library(readr)
library(dplyr)
library(dbscan)
library(leaflet)
library(sf)
library(geojsonio)
library(RColorBrewer)
coords <- df_clean %>%
select(Longitude, Latitude) %>%
as.matrix()
# Paramètres DBSCAN
eps_values <- seq(0.01, 0.02, by = 0.01)
minPts_values <- seq(150, 100, by = -10)
carte <- leaflet() %>%
addTiles(group = "Base") %>%
addWMSTiles(
baseUrl = "https://servicescarto.mern.gouv.qc.ca/pes/services/Territoire/SDA_WMS/MapServer/WMSServer",
layers = "Région administrative",
options = WMSTileOptions(format = "image/png", transparent = TRUE),
attribution = "© MERN - Gouvernement du Québec",
group = "Contours SDA"
)
# Liste pour garder le nom des groupes
group_names <- c()
# Générer les itérations de clustering
for (eps in eps_values) {
for (minPts in minPts_values) {
label <- sprintf("eps=%.2f_minPts=%d", eps, minPts)
group_names <- c(group_names, label)
# Appliquer DBSCAN
db <- dbscan(coords, eps = eps, minPts = minPts)
df_iter <- df_clean %>%
mutate(cluster = as.factor(db$cluster))
df_iter_grouped <- df_iter %>%
filter(cluster != 0)
# Ajouter le nombre de points par cluster
cluster_counts <- df_iter_grouped %>%
count(cluster, name = "n_points")
df_iter_grouped <- df_iter_grouped %>%
left_join(cluster_counts, by = "cluster")
# Ajouter à la carte dans un groupe spécifique
carte <- carte %>%
addCircleMarkers(
data = df_iter_grouped,
lng = ~Longitude,
lat = ~Latitude,
#color = ~ifelse(cluster == 0, "gray", RColorBrewer::brewer.pal(8, "Set1")[as.integer(cluster)]),
color = ~palette_Region_ADM_levels(Region_ADM),
radius = ~ifelse(cluster == 0, 1, 4),
fillOpacity = 0.8,
stroke = FALSE,
group = label,
label = ~paste("Cluster:", cluster, ": Nb points:", n_points, "Coût estimé groupe : ", formatC(n_points * 5500, format = "f", big.mark = " ", digits = 0), " $")
)
}
}La carte ci-dessous permet de visualiser les résultats de l’algorithme DBSCAN appliqué aux données géographiques. Chaque point représente une adresse, et les couleurs indiquent les regroupements détectés par l’algorithme.
Un sélecteur interactif situé dans la carte permet de choisir un scénario particulier, c’est-à-dire une combinaison spécifique des paramètres eps (rayon de voisinage) et minPts (nombre minimal de points).
Afin de déterminer les paramètres optimaux pour l’algorithme DBSCAN, plusieurs itérations ont été effectuées en faisant varier les valeurs de eps (rayon de voisinage) et de minPts (nombre minimal de points requis pour former un regroupement). Chaque combinaison de paramètres a permis de générer une partition différente des données géographiques de la province, produisant des regroupements (clusters) de points potentiellement intéressants pour le déploiement de la fibre optique.
Pour chaque itération, les statistiques suivantes ont été calculées :
Le nombre total de points regroupés (c’est-à-dire les points inclus dans un cluster) ;
Le nombre de clusters identifiés, excluant le bruit ;
Le pourcentage de points regroupés, un indicateur de la couverture ou de la densité apparente ;
Le nombre de points considérés comme du bruit, soit les points isolés ne satisfaisant pas les critères de densité.
Ces statistiques permettent de comparer objectivement les effets des différents paramètres et d’identifier les combinaisons les plus efficaces pour faire émerger des zones d’intérêt bien définies. Les résultats ont été compilés dans un tableau comparatif “Statistiques par itération DBSCAN pour la province”, facilitant l’analyse et la sélection des itérations les plus pertinentes pour une application concrète sur le terrain.
# Statistiques par itération DBSCAN pour le province au
res_dbscan <- list()
# Refaire les itérations pour calculer les stats
for (eps in eps_values) {
for (minPts in minPts_values) {
label <- sprintf("eps=%.2f_minPts=%d", eps, minPts)
# Appliquer DBSCAN
db <- dbscan(coords, eps = eps, minPts = minPts)
clusters <- db$cluster
n_total <- length(clusters)
n_bruit <- sum(clusters == 0)
n_regroupes <- n_total - n_bruit
n_clusters <- length(unique(clusters)) - ifelse(any(clusters == 0), 1, 0)
cout_total <- n_regroupes * 5500
cout_millions <- round(cout_total / 1e6) # arrondi au million près
cout_millions_fmt <- paste0(cout_millions, " M", "$")
pourcentage_regroupes <- (n_regroupes / n_total) * 100
res_dbscan[[label]] <- data.frame(
Paramètres = label,
Nombre_de_clusters = n_clusters,
Nombre_de_points_regroupes = n_regroupes,
Pourcentage_de_points_regroupes = round(pourcentage_regroupes, 1),
Cout_des_projets = cout_millions_fmt
)
}
}
# Combiner tous les résultats en un seul tableau
df_stats_dbscan <- do.call(rbind, res_dbscan)
library(knitr)
library(kableExtra)
# Afficher le tableau avec bordure
kable(df_stats_dbscan, caption = "Résumé statistique pour chaque itération de DBSCAN", format = "html") %>%
kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)| Paramètres | Nombre_de_clusters | Nombre_de_points_regroupes | Pourcentage_de_points_regroupes | Cout_des_projets | |
|---|---|---|---|---|---|
| eps=0.01_minPts=150 | eps=0.01_minPts=150 | 3 | 707 | 1.6 | 4 M$ |
| eps=0.01_minPts=140 | eps=0.01_minPts=140 | 6 | 1202 | 2.8 | 7 M$ |
| eps=0.01_minPts=130 | eps=0.01_minPts=130 | 7 | 1351 | 3.1 | 7 M$ |
| eps=0.01_minPts=120 | eps=0.01_minPts=120 | 9 | 1626 | 3.8 | 9 M$ |
| eps=0.01_minPts=110 | eps=0.01_minPts=110 | 12 | 2101 | 4.9 | 12 M$ |
| eps=0.01_minPts=100 | eps=0.01_minPts=100 | 19 | 2951 | 6.9 | 16 M$ |
| eps=0.02_minPts=150 | eps=0.02_minPts=150 | 10 | 3277 | 7.6 | 18 M$ |
| eps=0.02_minPts=140 | eps=0.02_minPts=140 | 13 | 3898 | 9.1 | 21 M$ |
| eps=0.02_minPts=130 | eps=0.02_minPts=130 | 18 | 4777 | 11.1 | 26 M$ |
| eps=0.02_minPts=120 | eps=0.02_minPts=120 | 20 | 5221 | 12.1 | 29 M$ |
| eps=0.02_minPts=110 | eps=0.02_minPts=110 | 27 | 6212 | 14.4 | 34 M$ |
| eps=0.02_minPts=100 | eps=0.02_minPts=100 | 33 | 6939 | 16.1 | 38 M$ |
Afin d’affiner l’analyse territoriale, les résultats du clustering ont été ventilés par région administrative. Cette décomposition permet de mieux comprendre la répartition des regroupements à l’échelle locale et de comparer l’efficacité des paramètres de DBSCAN selon les contextes géographiques. Pour chaque région et chaque itération, des statistiques similaires à celles calculées à l’échelle provinciale ont été compilées, facilitant ainsi l’identification des zones prioritaires à l’intérieur de chaque territoire.
# Extraire les minPts et eps depuis les labels
df_stats_dbscan <- df_stats_dbscan %>%
mutate(
eps = as.numeric(sub(".*eps=([0-9.]+)_minPts=.*", "\\1", rownames(df_stats_dbscan))),
minPts = as.numeric(sub(".*minPts=([0-9]+)", "\\1", rownames(df_stats_dbscan)))
)
library(ggplot2)
# Nuage de points : taille selon Nombre de clusters, couleur selon % de points regroupés
ggplot(df_stats_dbscan, aes(x = minPts, y = eps)) +
geom_point(aes(size = Nombre_de_clusters, color = Pourcentage_de_points_regroupes), alpha = 0.8) +
geom_text(aes(label = paste0(Pourcentage_de_points_regroupes, "%")), vjust = -0.5, size = 3) +
scale_color_gradient(low = "red", high = "green") +
scale_size_continuous(range = c(3, 10)) + # contrôle la plage de taille
scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) +
scale_x_reverse() +
labs(
title = "Nuage de points: minPts vs eps",
subtitle = "Taille = Nombre de clusters, Couleur = % de points regroupés",
x = "Nombre minimum de points (minPts)",
y = "Distance maximum (eps)",
color = "% de points regroupés",
size = "Nombre de clusters"
) +
theme_minimal()# DBSCAN par région
regions <- unique(df_clean$Region_ADM)
res_dbscan_region <- list()
for (region in regions) {
df_region <- df_clean %>% filter(Region_ADM == region)
coords_region <- df_region %>%
select(Longitude, Latitude) %>%
as.matrix()
for (eps in eps_values) {
for (minPts in minPts_values) {
label <- sprintf("eps=%.2f_minPts=%d", eps, minPts)
# Appliquer DBSCAN sur la région
db <- dbscan(coords_region, eps = eps, minPts = minPts)
clusters <- db$cluster
n_total <- length(clusters)
n_bruit <- sum(clusters == 0)
n_regroupes <- n_total - n_bruit
n_clusters <- length(unique(clusters)) - ifelse(any(clusters == 0), 1, 0)
pourcentage_regroupes <- (n_regroupes / n_total) * 100
cout_millions <- round((n_regroupes * 55000) / 1e6)
etiquette <- paste0(round(pourcentage_regroupes, 1), "%\n$", cout_millions, " M")
res_dbscan_region[[paste(region, label)]] <- data.frame(
Region_ADM = region,
eps = eps,
minPts = minPts,
Nombre_de_clusters = n_clusters,
Nombre_de_points_regroupes = n_regroupes,
Pourcentage_de_points_regroupes = round(pourcentage_regroupes, 2),
Etiquette = etiquette
)
}
}
}
library(ggforce)
# Combiner tous les résultats
df_stats_dbscan_region <- do.call(rbind, res_dbscan_region)
# Déterminer le nombre total de régions
n_regions <- length(unique(df_stats_dbscan_region$Region_ADM))
# Déterminer le nombre de pages (2 régions par page)
n_pages <- ceiling(n_regions / 2)
for (i in 1:n_pages) {
p <- ggplot(df_stats_dbscan_region, aes(x = minPts, y = eps)) +
geom_point(aes(size = Nombre_de_clusters, color = Pourcentage_de_points_regroupes), alpha = 0.8) +
geom_text(aes(label = Etiquette), vjust = -0.5, size = 2.5, check_overlap = TRUE) +
scale_color_gradient(low = "red", high = "green") +
scale_size_continuous(range = c(2, 8)) +
scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) +
scale_x_reverse() +
ggforce::facet_wrap_paginate(
~Region_ADM,
ncol = 1,
nrow = 2,
page = i
) +
labs(
title = "Clustering DBSCAN par Région administrative",
subtitle = paste("Page", i, "sur", n_pages),
x = "Nombre minimum de points (minPts)",
y = "Distance maximum (eps)",
color = "% de points regroupés",
size = "Nombre de clusters"
) +
theme_minimal()
print(p)
}Pour bien comprendre comment l’algorithme DBSCAN se comporte, j’ai mesuré le temps qu’il prend pour s’exécuter avec différentes combinaisons de paramètres.
Les deux paramètres importants sont :
eps : c’est la distance maximale pour que des points soient considérés comme voisins ;
minPts : c’est le nombre minimum de voisins requis pour former un regroupement.
Pour chaque combinaison possible de ces deux paramètres, l’algorithme a été lancé, et on a mesuré combien de temps il a pris. On obtient ainsi un aperçu de la charge de calcul selon les paramètres choisis.
Il faut savoir que les résultats peuvent varier d’un ordinateur à l’autre. Par exemple, un ordinateur avec un processeur plus rapide ou plus de mémoire pourra exécuter l’algorithme plus rapidement. Même sur un même ordinateur, les temps peuvent changer selon les logiciels ouverts ou les tâches en cours.
Autrement dit, les temps affichés dans l’analyse donnent une bonne idée des tendances générales, mais ce ne sont pas des chiffres fixes. Ils servent à comparer entre eux les différents réglages de DBSCAN, pour mieux choisir ceux qui sont efficaces et rapides.
# Tester la performance du calcul DBSCAN
library(tibble) # pour tibble::tibble si besoin
# Créer une liste pour stocker les temps
perf_dbscan <- list()
eps_values <- seq(0.001, 0.05, by = 0.01) # 0.01 à 0.10 (10 valeurs)
minPts_values <- seq(5, 55, by = 5) # 10 à 100 (10 valeurs)
# Mesurer les temps pour chaque combinaison de paramètres
for (eps in eps_values) {
for (minPts in minPts_values) {
start_time <- Sys.time()
db <- dbscan(coords, eps = eps, minPts = minPts)
end_time <- Sys.time()
elapsed <- as.numeric(difftime(end_time, start_time, units = "secs"))
label <- sprintf("eps=%.2f_minPts=%d", eps, minPts)
perf_dbscan[[label]] <- tibble(
Paramètres = label,
eps = eps,
minPts = minPts,
Temps_en_secondes = round(elapsed, 4)
)
}
}
# Combiner tous les résultats dans un data frame
df_perf_dbscan <- do.call(rbind, perf_dbscan)
# Afficher le tableau des temps de calcul
knitr::kable(df_perf_dbscan, caption = "Temps de calcul pour chaque itération de DBSCAN (en secondes)")| Paramètres | eps | minPts | Temps_en_secondes |
|---|---|---|---|
| eps=0.00_minPts=5 | 0.001 | 5 | 0.0199 |
| eps=0.00_minPts=10 | 0.001 | 10 | 0.0183 |
| eps=0.00_minPts=15 | 0.001 | 15 | 0.0178 |
| eps=0.00_minPts=20 | 0.001 | 20 | 0.0178 |
| eps=0.00_minPts=25 | 0.001 | 25 | 0.0183 |
| eps=0.00_minPts=30 | 0.001 | 30 | 0.0185 |
| eps=0.00_minPts=35 | 0.001 | 35 | 0.0181 |
| eps=0.00_minPts=40 | 0.001 | 40 | 0.0177 |
| eps=0.00_minPts=45 | 0.001 | 45 | 0.0176 |
| eps=0.00_minPts=50 | 0.001 | 50 | 0.0184 |
| eps=0.00_minPts=55 | 0.001 | 55 | 0.0187 |
| eps=0.01_minPts=5 | 0.011 | 5 | 0.0313 |
| eps=0.01_minPts=10 | 0.011 | 10 | 0.0311 |
| eps=0.01_minPts=15 | 0.011 | 15 | 0.0308 |
| eps=0.01_minPts=20 | 0.011 | 20 | 0.0293 |
| eps=0.01_minPts=25 | 0.011 | 25 | 0.0289 |
| eps=0.01_minPts=30 | 0.011 | 30 | 0.0279 |
| eps=0.01_minPts=35 | 0.011 | 35 | 0.0285 |
| eps=0.01_minPts=40 | 0.011 | 40 | 0.0288 |
| eps=0.01_minPts=45 | 0.011 | 45 | 0.0289 |
| eps=0.01_minPts=50 | 0.011 | 50 | 0.0284 |
| eps=0.01_minPts=55 | 0.011 | 55 | 0.0288 |
| eps=0.02_minPts=5 | 0.021 | 5 | 0.0418 |
| eps=0.02_minPts=10 | 0.021 | 10 | 0.0408 |
| eps=0.02_minPts=15 | 0.021 | 15 | 0.0394 |
| eps=0.02_minPts=20 | 0.021 | 20 | 0.0374 |
| eps=0.02_minPts=25 | 0.021 | 25 | 0.0372 |
| eps=0.02_minPts=30 | 0.021 | 30 | 0.0381 |
| eps=0.02_minPts=35 | 0.021 | 35 | 0.0378 |
| eps=0.02_minPts=40 | 0.021 | 40 | 0.0366 |
| eps=0.02_minPts=45 | 0.021 | 45 | 0.0379 |
| eps=0.02_minPts=50 | 0.021 | 50 | 0.0377 |
| eps=0.02_minPts=55 | 0.021 | 55 | 0.0380 |
| eps=0.03_minPts=5 | 0.031 | 5 | 0.0487 |
| eps=0.03_minPts=10 | 0.031 | 10 | 0.0489 |
| eps=0.03_minPts=15 | 0.031 | 15 | 0.0482 |
| eps=0.03_minPts=20 | 0.031 | 20 | 0.0487 |
| eps=0.03_minPts=25 | 0.031 | 25 | 0.0477 |
| eps=0.03_minPts=30 | 0.031 | 30 | 0.0483 |
| eps=0.03_minPts=35 | 0.031 | 35 | 0.0475 |
| eps=0.03_minPts=40 | 0.031 | 40 | 0.0474 |
| eps=0.03_minPts=45 | 0.031 | 45 | 0.0463 |
| eps=0.03_minPts=50 | 0.031 | 50 | 0.0454 |
| eps=0.03_minPts=55 | 0.031 | 55 | 0.0471 |
| eps=0.04_minPts=5 | 0.041 | 5 | 0.0600 |
| eps=0.04_minPts=10 | 0.041 | 10 | 0.0583 |
| eps=0.04_minPts=15 | 0.041 | 15 | 0.0601 |
| eps=0.04_minPts=20 | 0.041 | 20 | 0.0581 |
| eps=0.04_minPts=25 | 0.041 | 25 | 0.0592 |
| eps=0.04_minPts=30 | 0.041 | 30 | 0.0577 |
| eps=0.04_minPts=35 | 0.041 | 35 | 0.0576 |
| eps=0.04_minPts=40 | 0.041 | 40 | 0.0582 |
| eps=0.04_minPts=45 | 0.041 | 45 | 0.0574 |
| eps=0.04_minPts=50 | 0.041 | 50 | 0.0570 |
| eps=0.04_minPts=55 | 0.041 | 55 | 0.0563 |
ggplot(df_perf_dbscan, aes(x = minPts, y = eps)) +
geom_point(aes(size = Temps_en_secondes, color = Temps_en_secondes), alpha = 0.8) +
scale_color_gradient(low = "blue", high = "red") +
scale_size_continuous(range = c(2, 10)) +
labs(
title = "Temps de calcul de DBSCAN",
subtitle = "Taille et couleur = temps de calcul (en secondes)",
x = "minPts",
y = "eps",
size = "Temps (s)",
color = "Temps (s)"
) +
theme_minimal()# Charger plotly
if (!require("plotly")) install.packages("plotly")
library(plotly)
# Graphique 3D interactif des temps de calcul DBSCAN
plot_ly(
df_perf_dbscan,
x = ~minPts,
y = ~eps,
z = ~Temps_en_secondes,
type = "scatter3d",
mode = "markers",
marker = list(
size = 4,
color = ~Temps_en_secondes,
colorscale = "Viridis",
showscale = TRUE
)
) %>%
layout(
title = "Temps de calcul DBSCAN (3D)",
scene = list(
xaxis = list(title = "minPts"),
yaxis = list(title = "eps"),
zaxis = list(title = "Temps (secondes)")
)
)# Définir l'étendue des coordonnées pour générer les jeux de comparaison
min_lon <- min(coords[,1], na.rm = TRUE)
max_lon <- max(coords[,1], na.rm = TRUE)
min_lat <- min(coords[,2], na.rm = TRUE)
max_lat <- max(coords[,2], na.rm = TRUE)
n_points <- nrow(coords)
# Générer coordonnées aléatoires
coords_random <- cbind(
runif(n_points, min_lon, max_lon),
runif(n_points, min_lat, max_lat)
)
# Générer une grille régulière de points (approximation du même nombre)
grid_size <- ceiling(sqrt(n_points))
x_seq <- seq(min_lon, max_lon, length.out = grid_size)
y_seq <- seq(min_lat, max_lat, length.out = grid_size)
coords_grid <- expand.grid(x_seq, y_seq)
coords_grid <- as.matrix(coords_grid[1:n_points, ])
# Créer une fonction générique de mesure du temps DBSCAN
tester_dbscan <- function(coords_test, tag) {
results <- list()
for (eps in eps_values) {
for (minPts in minPts_values) {
start_time <- Sys.time()
db <- dbscan(coords_test, eps = eps, minPts = minPts)
end_time <- Sys.time()
elapsed <- as.numeric(difftime(end_time, start_time, units = "secs"))
label <- sprintf("eps=%.2f_minPts=%d", eps, minPts)
results[[label]] <- tibble(
Paramètres = label,
eps = eps,
minPts = minPts,
Temps_en_secondes = round(elapsed, 4),
Jeu = tag
)
}
}
do.call(rbind, results)
}
# Appliquer la fonction aux trois jeux de données
df_perf_reel <- tester_dbscan(coords, "Données réelles")
df_perf_random <- tester_dbscan(coords_random, "Coordonnées aléatoires")
df_perf_grille <- tester_dbscan(coords_grid, "Grille régulière")
# Fusionner les résultats
df_perf_complet <- bind_rows(df_perf_reel, df_perf_random, df_perf_grille)
ggplot(df_perf_complet, aes(x = minPts, y = eps)) +
geom_point(aes(size = Temps_en_secondes, color = Temps_en_secondes), alpha = 0.7) +
scale_color_gradient(low = "blue", high = "red") +
scale_size_continuous(range = c(2, 8)) +
labs(
title = "Temps de calcul de DBSCAN selon la distribution spatiale",
subtitle = "Comparaison des données réelles, aléatoires et régulières",
x = "minPts", y = "eps",
color = "Temps (s)", size = "Temps (s)"
) +
facet_wrap(~Jeu) +
theme_minimal()L’analyse comparative des temps d’exécution de l’algorithme DBSCAN selon la structure spatiale des données révèle des différences significatives :
Données réelles : Ces données présentent des regroupements naturels avec des densités hétérogènes. Le temps de traitement est généralement intermédiaire, mais il varie davantage selon les paramètres choisis (eps, minPts). Cela reflète le besoin du modèle de détecter des structures complexes et irrégulières.
Coordonnées aléatoires : Dans ce scénario, les points sont dispersés de façon uniforme. DBSCAN a peu ou pas de regroupements à détecter, ce qui entraîne un temps de calcul globalement plus faible. Les variations selon les paramètres sont aussi plus prévisibles, car la distribution ne favorise pas la formation de noyaux denses.
Grille régulière : Ici, les points sont espacés équitablement. Cela ralentit légèrement l’exécution comparativement au nuage aléatoire, notamment lorsque eps est proche de la distance entre les points, car le modèle explore davantage de voisinages. C’est la configuration où les temps d’exécution peuvent dépasser ceux des données réelles, surtout si l’algorithme essaie sans succès de regrouper des points trop distants.
Ces résultats montrent que la géométrie des données influence fortement la performance de DBSCAN. Cela souligne l’importance de :
comprendre la structure spatiale des jeux de données avant l’analyse,
tester différents paramètres dans des contextes simulés,
et optimiser les calculs selon le degré de dispersion des données d’entrée.
L’analyse des résultats a révélé plusieurs trous d’information dans le jeu de données, qui soulèvent des enjeux quant à la fiabilité et l’actualité des données disponibles :
Absence de données sur certaines rues. Plusieurs segments de rue ne présentent aucun point, bien qu’ils soient bel et bien construits et habités. Cela laisse croire à une intégration incomplète de certaines adresses dans les bases de données géographiques.
Exemples concrets à Sherbrooke :
Rue Hermann-Morin : absente malgré sa présence dans un secteur résidentiel récent.
Rue Berger : aucune donnée recensée alors qu’elle figure clairement sur les cartes officielles.
Ces absences nuisent à l’évaluation complète de la couverture et peuvent introduire un biais géographique dans les résultats.
Lacunes en milieu urbain Même dans des centres urbains denses comme Québec, Laval et Montréal, on observe des trous de couverture inattendus. Certains quartiers entiers apparaissent sans aucun point, ce qui est peu plausible compte tenu de la densité réelle d’adresses.
Ces lacunes peuvent être attribuées à des retards de mise à jour des bases d’adresses ou à des limitations techniques dans le processus de traitement initial.
Fin des incitatifs pour les fournisseurs Il est également important de noter que les subventions du programme Internet Haute Vitesse du MCE (SIHVPSC) sont maintenant terminées. Ce programme, qui obligeait les fournisseurs à transmettre leurs adresses desservies en échange d’un financement, n’est plus actif.
En conséquence :
plusieurs fournisseurs n’ont plus de motivation réglementaire à fournir leurs données d’adressage ou à maintenir leur exactitude. Cela contribue probablement à la stagnation ou à la détérioration de la qualité des données adressées dans certains territoires, en particulier les plus récemment développés.
Ainsi, bien que cette estimation donne un ordre de grandeur utile pour la planification stratégique, elle doit être considérée avec prudence et accompagnée d’analyses spécifiques sur le terrain pour toute mise en œuvre opérationnelle.
Le projet réalisé a permis de démontrer concrètement l’utilité et la flexibilité de l’algorithme DBSCAN pour l’analyse spatiale de données géographiques. En explorant différentes combinaisons de paramètres eps et minPts, j’ai pu identifier des configurations qui produisent des regroupements pertinents, tout en évaluant leur impact sur la performance du calcul.
Grâce à une approche rigoureuse et itérative, enrichie par des visualisations interactives, il a été possible de mieux comprendre les dynamiques de regroupement présentes dans les données. La carte interactive des scénarios DBSCAN s’est révélée particulièrement efficace pour comparer visuellement les effets des paramètres et pour détecter des zones denses, dispersées ou mal couvertes. La possibilité de naviguer parmi les différents scénarios offre une interface intuitive pour explorer les résultats et orienter la prise de décision.
L’intégration d’une analyse régionale, tant au niveau visuel qu’au niveau statistique, a permis de mettre en lumière des écarts significatifs entre les régions administratives. Cela souligne l’importance d’adapter les paramètres ou les stratégies de regroupement selon les particularités régionales, plutôt que d’appliquer une solution uniforme.
Par ailleurs, l’évaluation des performances a permis de mieux cerner le compromis entre qualité des regroupements et efficacité du calcul.
En somme, ce projet met en valeur une méthode robuste pour le traitement de données géospatiales, et démontre qu’avec les bons outils et une démarche structurée, il est possible de tirer un maximum d’information de jeux de données complexes. Les résultats obtenus sont non seulement prometteurs pour les besoins du mandat actuel, mais ils ouvrent aussi la porte à des usages futurs, que ce soit pour la planification, la gestion de réseaux, ou le soutien à la prise de décision.
Ce travail illustre aussi la puissance du langage R pour combiner traitement algorithmique, analyse statistique et production de visualisations avancées dans un seul environnement reproductible. Il constitue une base solide pour aller encore plus loin dans l’automatisation, la personnalisation des analyses, ou encore l’intégration avec d’autres sources de données.
Dans une perspective de prolongement, une analyse économique approfondie pourrait être envisagée. Celle-ci intégrerait non seulement la structure des regroupements identifiés, mais aussi les distances physiques entre ceux-ci, en lien avec les infrastructures existantes, notamment le réseau de transport de fibre optique. En analysant également les possibilités de redondance et de continuité de service, il serait possible de dégager des orientations optimales en termes de coûts, de résilience et de couverture territoriale. Ce type d’approche multidimensionnelle offrirait une vision encore plus stratégique et applicable des résultats obtenus.
Je tiens à remercier sincèrement tous les professeurs, personnes ressources en encadrement et tuteurs de l’Université TÉLUQ pour la qualité exceptionnelle de l’enseignement reçu tout au long du certificat en science des données. Votre approche rigoureuse, accessible et résolument avant-gardiste a grandement contribué à mon apprentissage et à mon développement professionnel. Merci pour votre engagement et votre passion.
Je tiens aussi à exprimer ma profonde gratitude envers l’équipe du Secrétariat à l’Internet haute Vitesse et projets spéciaux en connectivité du Ministère du Conseil exécutif. Votre vision innovante, votre confiance et votre volonté constante de repousser les limites en matière de connectivité ont été une source d’inspiration tout au long de mon parcours. Merci pour votre engagement avant-gardiste au service du développement du numérique au Québec.
https://www.quebec.ca/gouvernement/ministeres-organismes/sihv↩︎
Consulter la carte https://www.quebec.ca/habitation-territoire/amenagement-developpement-territoires/carte-internet-haute-vitesse↩︎
Voir https://fr.wikipedia.org/wiki/DBSCAN pour plus d’information.↩︎
Résumé des subventions dans le cadre du déploiments de l’internet haute vitesse du gouvernement du Québec https://cdn-contenu.quebec.ca/cdn-contenu/adm/org/secretariat-internet-haute-vitesse/operation-haute-vitesse/Tableau_fournisseurs_de_services_Internet.pdf↩︎